CREATING AN INTERACTIVE WELL TRAJECTORY PLOT IN PYTHON¶

Introduction¶

A wellbore trajectory is a roadmap for drilling wells in the oil and gas industry. It shows the path from the surface to the underground reservoir, so that drillers know where to go. This information is crucial for efficient and safe drilling. It ensures that drillers reach the right spot to extract oil and gas resources.

About the Data: Wellbore Trajectory from Volve Dataset in WITSML Format¶

Four XML data files were used to create an interactive wellbore trajectory plot for Well 15/9_F-5. The data was retrieved from the Volve dataset in the Wellsite Information Transfer Standard Markup Language (WITSML) folder. XML (Extensible Markup Language) is a digital file format that makes it easy to share well information between different computer systems and companies.

Python Codes behind the generated interactive well trajectory plot¶

We import a few Python libraries that have specific functions to make data processing and visualization smooth sailing in this code process:

  • Numpy: A library that provides tools for numerical computations.
  • BeautifulSoup: A library from the bs4 module, which helps us parse HTML and XML files. In this case, we're using the XML files.
  • Pandas: A library that offers versatile capabilities for data manipulation and analysis.
  • Plotly.graph_objects: A module that provides a simple way to create interactive plots and visualizations.
  • Plotly.offline: A module that allows us to generate offline plots and save them as HTML files.

By importing these libraries, we can efficiently handle data and create interactive visualizations as we move forward in the code.

In [1]:
# Import python libraries
import numpy as np
from bs4 import BeautifulSoup
import pandas as pd
import plotly.graph_objects as go
from plotly.offline import plot

The following code is in charge of retrieving the well trajectory data from XML files for Well 15/9-F-5. The trajectory data is distributed across four XML files, specifically named '1.xml', '2.xml', '3.xml', and '4.xml'. The variable Trajectory_files holds the file names, while the legend_names variable captures the descriptive labels for different sections of the well trajectory. These legend names offer valuable context about each section of the well trajectory, enhancing the understanding of the data.

In [2]:
# Read the Well 15/9-F-5 Trajectory XML files
Trajectory_files = ['1.xml', '2.xml', '3.xml', '4.xml']
legend_names = ['17.5 in. Section (T-574976-1)', '12.25 in. Section (T-704051-1)', '8.5 in. Section (T-710489-1)', 
                '36 in. Section (T-555809-1)'] # Taken from the MetaDataFileInfo.txt file included in the folder

Now, let's move on to the next step. The code below manages in handling the task of reading XML files that contain data related to the trajectory of a well. It makes use of the BeautifulSoup library, which provides convenient tools for processing and extracting information from XML files.

To begin, an empty list named data_xml is created to hold the parsed XML data. The code then iterates through each file name in the Trajectory_files list. For each file, it opens the XML file, reads its contents, and assigns them to the variable data.

Using the BeautifulSoup library, the code creates a BeautifulSoup object called data_xml by passing in the data and specifying the parser type as XML. This object represents the parsed XML data from the file. The data_xml object is then added to the data_xml list.

The data_xml list now contains a collection of BeautifulSoup objects, with each object representing the parsed XML data from a specific file. These objects can be further processed and analyzed to extract the desired information from the XML files containing the well trajectory data.

In [3]:
# Reading XML Files into BeautifulSoup Objects
data_xml = []

for file in Trajectory_files:
    with open(file) as f:
        data = f.read()
        data_xml.append(BeautifulSoup(data, 'xml'))

Let's proceed to the next step. The following code is used to extract and display the data columns from the XML files that hold information about the well trajectory. It iterates over the data_xml list, which contains the parsed XML data, and for each file, it retrieves and prints the data columns. The extraction process involves finding all the tags in the XML data, and the resulting columns are then displayed as output. By executing this code, we see the complete list of columns for all four XML files.

In [4]:
# Printing the data columns of the XML files
for i, data in enumerate(data_xml):
    print(f"Data columns of {Trajectory_files[i]}:")
    columns = set([tag.name for tag in data.find_all()])
    print("\n".join(columns))
    print()
Data columns of 1.xml:
dTimTrajStart
azi
sagIncCor
gravAxialRaw
sourceName
gravTran1Raw
nameWell
md
gravTotalFieldCalc
rawData
trajectoryStation
magTotalFieldCalc
aziVertSect
commonData
magTran1DrlstrCor
magTotalFieldReference
magDrlstrCorUsed
stnMagDeclUsed
dTimStn
dispNs
gravTotalFieldReference
gravTran1AccelCor
mdDelta
statusTrajStation
magDipAngleCalc
tvd
nameWellbore
itemState
priv_ipOwner
gravAxialAccelCor
corUsed
vertSect
stnGridCorUsed
sagCorUsed
rateBuild
incl
gravTotalUncert
magTran1Raw
sagAziCor
trajectory
magAxialRaw
gtf
gridCorUsed
magTran2DrlstrCor
magXAxialCorUsed
dispEw
mdMx
trajectorys
gravTran2Raw
priv_dTimReceived
rateTurn
mdMn
magTran2Raw
valid
magAxialDrlstrCor
memory
typeTrajStation
dls
dTimLastChange
priv_userOwner
dTimCreation
name
dTimTrajEnd
dirSensorOffset
magDeclUsed
magTotalUncert
tvdDelta
serviceCompany
gravTran2AccelCor
dipAngleUncert
aziRef
mtf
gravAccelCorUsed
magDipAngleReference

Data columns of 2.xml:
dTimTrajStart
azi
sagIncCor
gravAxialRaw
sourceName
gravTran1Raw
nameWell
md
gravTotalFieldCalc
rawData
trajectoryStation
magTotalFieldCalc
aziVertSect
commonData
magTran1DrlstrCor
magTotalFieldReference
magDrlstrCorUsed
stnMagDeclUsed
dTimStn
dispNs
gravTotalFieldReference
gravTran1AccelCor
mdDelta
statusTrajStation
magDipAngleCalc
tvd
nameWellbore
itemState
priv_ipOwner
gravAxialAccelCor
corUsed
vertSect
stnGridCorUsed
sagCorUsed
rateBuild
incl
gravTotalUncert
magTran1Raw
sagAziCor
trajectory
magAxialRaw
gtf
gridCorUsed
magTran2DrlstrCor
magXAxialCorUsed
dispEw
mdMx
trajectorys
gravTran2Raw
priv_dTimReceived
rateTurn
mdMn
magTran2Raw
valid
magAxialDrlstrCor
memory
typeTrajStation
dls
dTimLastChange
priv_userOwner
dTimCreation
name
dTimTrajEnd
dirSensorOffset
magDeclUsed
magTotalUncert
tvdDelta
serviceCompany
gravTran2AccelCor
dipAngleUncert
aziRef
mtf
gravAccelCorUsed
magDipAngleReference

Data columns of 3.xml:
dTimTrajStart
azi
sagIncCor
gravAxialRaw
sourceName
gravTran1Raw
nameWell
md
gravTotalFieldCalc
rawData
trajectoryStation
magTotalFieldCalc
aziVertSect
commonData
magTran1DrlstrCor
magTotalFieldReference
magDrlstrCorUsed
stnMagDeclUsed
dTimStn
dispNs
gravTotalFieldReference
gravTran1AccelCor
mdDelta
statusTrajStation
magDipAngleCalc
tvd
nameWellbore
itemState
priv_ipOwner
gravAxialAccelCor
corUsed
vertSect
stnGridCorUsed
sagCorUsed
rateBuild
incl
gravTotalUncert
magTran1Raw
sagAziCor
trajectory
magAxialRaw
gtf
gridCorUsed
magTran2DrlstrCor
magXAxialCorUsed
dispEw
mdMx
trajectorys
gravTran2Raw
priv_dTimReceived
rateTurn
mdMn
magTran2Raw
valid
magAxialDrlstrCor
memory
typeTrajStation
dls
dTimLastChange
priv_userOwner
dTimCreation
name
dTimTrajEnd
dirSensorOffset
magDeclUsed
magTotalUncert
tvdDelta
serviceCompany
gravTran2AccelCor
dipAngleUncert
aziRef
mtf
gravAccelCorUsed
magDipAngleReference

Data columns of 4.xml:
dTimTrajStart
azi
sagIncCor
gravAxialRaw
sourceName
gravTran1Raw
nameWell
md
gravTotalFieldCalc
rawData
trajectoryStation
magTotalFieldCalc
aziVertSect
commonData
magTran1DrlstrCor
magTotalFieldReference
magDrlstrCorUsed
stnMagDeclUsed
dTimStn
dispNs
gravTotalFieldReference
gravTran1AccelCor
mdDelta
statusTrajStation
magDipAngleCalc
tvd
nameWellbore
itemState
priv_ipOwner
gravAxialAccelCor
corUsed
vertSect
stnGridCorUsed
sagCorUsed
rateBuild
incl
gravTotalUncert
magTran1Raw
sagAziCor
trajectory
magAxialRaw
gtf
gridCorUsed
magTran2DrlstrCor
magXAxialCorUsed
dispEw
mdMx
trajectorys
gravTran2Raw
priv_dTimReceived
rateTurn
mdMn
magTran2Raw
valid
magAxialDrlstrCor
memory
typeTrajStation
dls
dTimLastChange
priv_userOwner
dTimCreation
name
dTimTrajEnd
dirSensorOffset
magDeclUsed
magTotalUncert
tvdDelta
serviceCompany
gravTran2AccelCor
dipAngleUncert
aziRef
mtf
gravAccelCorUsed
magDipAngleReference

Based on the result above, we have obtained a comprehensive list of column names present in the XML files. Now, we select the specific column names to be utilized in plotting and visualizing the data.

Following the code below is responsible for extracting the data columns from the XML files. It initializes an empty list called 'dfs' to store the extracted data frames. By iterating over the 'data_xml' objects, it creates a new data frame for each XML file. The specified columns, namely 'azi', 'md', 'tvd', 'incl', 'dispNs', and 'dispEw', are extracted from the XML data and stored in their respective columns within the data frame. So, each data frame is appended to the 'dfs' list for further processing.

In [5]:
# Extracting Data Columns from XML Files
columns = ['azi', 'md', 'tvd', 'incl', 'dispNs', 'dispEw']
dfs = []

for i, data in enumerate(data_xml):
    df = pd.DataFrame()
    for col in columns:
        df[col] = [float(x.text) for x in data.find_all(col)]
    dfs.append(df)

Moving forward, the code below handles for generating traces that is used to create plots for each dataset. It starts by initializing an empty list called 'traces' to store the generated traces. The 'colors' list defines the colors to be assigned to each dataset, and the line width is set to 10.

Using a loop combined with the 'enumerate' function, the code iterates over the 'dfs' list, which contains the data frames. For each data frame, a 3D scatter trace is created using the 'go.Scatter3d' function from the Plotly library. The trace is defined by specifying the x, y, and z coordinates, setting the mode to lines, and configuring the line color and width based on the corresponding dataset index. The name of the trace is assigned using the 'legend_names' list. Each trace is added to the 'traces' list for further usage in the plotting process.

In [6]:
# Create traces for each dataset
traces = []
colors = ['red', 'blue', 'green', 'orange']
line_width = 10  # Adjust the line width here

for i, df in enumerate(dfs):
    trace = go.Scatter3d(x=df['dispNs'], y=df['dispEw'], z=df['tvd'] * -1, mode='lines',
                         line=dict(color=colors[i], width=line_width), name=legend_names[i])
    traces.append(trace)

Now comes the exciting part of this coding process. The following code is in charge of generating a visually appealing 3D plot of the well trajectory. It starts by creating a plot object using the 'go.Figure' function, and the 'traces' list, which holds the data to be plotted, is passed as input.

After that, the code proceeds to configure various plot settings. It sets the title, axis labels, and font sizes to ensure clear visualization. The aspect ratio is adjusted to maintain the proper proportions in the plot. Margins are specified to control the spacing around the plot. The legend is customized with a suitable title and font sizes.

Lastly, the plot is displayed for immediate viewing, and it is also saved as an HTML file for future reference or sharing. This allows us to conveniently explore and analyze the well trajectory in an interactive and visually appealing manner. The html file of the interactive well trajectory plot is available on the github repository for viewing.

In [7]:
# Create the figure
fig = go.Figure(data=traces)

# Set axis labels, title and the interactive trajectory plot settings
fig.update_layout(
    title=dict(text="WELL 15/9-F-5 TRAJECTORY<br>Field Name: Volve Field", x=0.4,  # Set the x position to center the title
               xanchor='center'),  # Center the title horizontally
    hovermode='closest',
    scene=dict(xaxis=dict(title='N-S DISPLACEMENT', titlefont=dict(size=20)), 
               yaxis=dict(title='E-W DISPLACEMENT', titlefont=dict(size=20)),
               zaxis=dict(title='TRUE VERTICAL DEPTH', titlefont=dict(size=20))),
    scene_aspectmode='manual',
    scene_aspectratio=dict(x=0.7, y=0.7, z=1.5),
    margin=dict(l=20, r=20, b=20, t=65),
    font=dict(size=15),
    legend=dict(title='ACTUAL TRAJECTORIES<br>Real Time SLB & Geoservice data', title_font=dict(size=18), font=dict(size=14)))

# Show the plot
fig.show()

# Save the plot as an HTML file
plot(fig, filename='Well Trajectory.html')
Out[7]:
'Well Trajectory.html'

Data Credits¶

Equinor ASA (formerly Statoil) and the former Volve license partners, ExxonMobil Exploration & Production Norway AS and Bayerngas Norge AS, are credited for providing the VOLVE dataset under CC BY 4.0 license.

Thank You for Visiting This GitHub Repository: Grateful for Your Interest¶

Thank you so much for visiting this GitHub repository and taking the time to read this notebook. I hope you found it informative and learned something new. Your support and interest are sincerely appreciated.